package com.hu.biz.util.lock;


import com.hu.biz.service.ICacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.SocketTimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @ClassName RenewTask
 * @Description 锁
 * @author 355760
 **/
public class RedisLock implements AutoCloseable {
    private static final Logger logger = LoggerFactory.getLogger("redis-lock");

    // 是否已经持有锁
    private final AtomicBoolean hold = new AtomicBoolean(false);

    private final ICacheService client;

    private final static String RESULT_OK = "OK";
    private final String key;
    private final String value;
    private Long ttl = 200L;
    private final Integer maxRetry = 5;
    private Boolean retryLock = false;

    /**
     * 续租线程
     */
    RenewTask renewalTask;

    public RedisLock(ICacheService client, String key, String value) {
        this.key = key;
        this.value = value;
        this.client = client;
    }

    public RedisLock(ICacheService client, String key, String value, Long ttl) {
        this.key = key;
        this.value = value;
        this.ttl = ttl;
        this.client = client;
    }

    public RedisLock(ICacheService client, String key, String value, Boolean retryLock) {
        this.key = key;
        this.value = value;
        this.client = client;
        this.retryLock = retryLock;
    }

    private boolean lock() {
        try {
            // 抢锁
            if (RESULT_OK.equals(client.setNxPx(key, value, this.ttl))) {
                renewalTask = new RenewTask(() -> {
                    // 刷新值
                    client.expire(key, ttl <= 0 ? 10 : ttl);
                }, ttl);
                renewalTask.setDaemon(true);
                renewalTask.start();
                hold.set(true);
            } else {
                hold.set(false);
            }
        } catch (Exception e) {
            hold.set(false);
            Throwable cause = e.getCause();
            if (cause instanceof SocketTimeoutException) {
                return hold.get();
            }
            logger.error("Error encountered when attempting to acquire lock", e);
            throw e;
        }

        return hold.get();
    }

    public boolean acquire(Long ttl, long interval, int maxRetry) {
        this.ttl = (ttl <= 0 ? 10L : ttl);
        try {
            if (!lock() && retryLock && maxRetry > 0) {
                Thread.sleep((interval * 1000) <= 0 ? 1 : (interval * 1000));
                return acquire(ttl, interval, maxRetry - 1);
            }
        } catch (Exception e) {
            if (maxRetry > 0) {
                try {
                    Thread.sleep((interval * 1000) <= 0 ? 1 : (interval * 1000));
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                return acquire(ttl, interval, maxRetry - 1);
            }
        }
        return hold.get();
    }

    public boolean tryLock() {
       return acquire(ttl, 1, maxRetry);
    }

    public void release() {
        try {
            close();
        } catch (Exception e) {
            logger.error("release lock exception", e);
        }
    }

    @Override
    public void close() throws Exception {
        if (hold.get()) {
            try {
                hold.set(false);
                client.del(key);
            } finally {
                if (renewalTask != null) {
                    renewalTask.close();
                }
            }
        }
    }
}
